#pragma once

#include "obj.h"
#include <type_traits>
#include <utility>

OPEN_JLIB_NS


template<typename Type>
class Ptr {

  static_assert(std::is_base_of<Obj, Type>::value, "");

public:
  inline Ptr () noexcept : _reference(nullptr) {}

  inline Ptr (Type *obj, std::nullptr_t) noexcept : _reference(obj) {}

  inline Ptr (Type* obj) noexcept : _reference(obj) {
    if (_reference)
      _reference->retain();
  }

  inline Ptr (const Ptr &other) noexcept : _reference(other._reference) {
    if (_reference)
      _reference->retain();
  }

  inline Ptr (Ptr &&other) noexcept : _reference(other._reference) {
    other._reference = nullptr;
  }

  inline ~Ptr () {
    if (_reference)
      _reference->release();
  }

  inline Type& ref () noexcept {
    debug_assert(_reference);
    return *_reference;
  }

  inline const Type& ref () const noexcept {
    debug_assert(_reference);
    return *_reference;
  }

  inline Type* ptr () noexcept {
    return _reference;
  }

  inline const Type* ptr () const noexcept {
    return _reference;
  }

  inline operator bool () const noexcept {
    return _reference != nullptr;
  }

  template<typename _Type>
  inline explicit operator _Type* () noexcept {
    static_assert(std::is_base_of<_Type, Type>::value || std::is_base_of<Type, _Type>::value, "");
#if !defined(NDEBUG) && defined(RTTI_ENABLED)
    return dynamic_cast<_Type*>(_reference);
#else
    return static_cast<_Type*>(_reference);
#endif
  }

  template<typename _Type>
  inline explicit operator const _Type* () const noexcept {
    static_assert(std::is_base_of<_Type, Type>::value || std::is_base_of<Type, _Type>::value, "");
#if !defined(NDEBUG) && defined(RTTI_ENABLED)
    return dynamic_cast<_Type*>(_reference);
#else
    return static_cast<_Type*>(_reference);
#endif
  }

  inline Ptr& operator= (std::nullptr_t) noexcept {
    if (_reference) {
      _reference->release();
      _reference = nullptr;
    }
    return *this;
  }

  inline Ptr& operator= (Type *obj) noexcept {
    if (obj)
      obj->retain();
    if (_reference)
      _reference->release();
    _reference = obj;
    return *this;
  }

  inline Ptr& operator= (const Ptr &other) noexcept {
    if (other._reference)
      other._reference->retain();
    if (_reference)
      _reference->release();
    _reference = other._reference;
    return *this;
  }

  inline Ptr& operator= (Ptr &&other) noexcept {
    if (_reference)
      _reference->release();
    _reference = other._reference;
    other._reference = nullptr;
    return *this;
  }

  inline bool operator== (const Ptr &other) const noexcept {
    return _reference == other._reference;
  }

  inline bool operator== (const Type *obj) const noexcept {
    return _reference == obj;
  }

  inline bool operator!= (const Ptr &other) const noexcept {
    return _reference != other._reference;
  }

  inline bool operator!= (const Type *obj) const noexcept {
    return _reference != obj;
  }

  inline bool operator> (const Ptr &other) const noexcept {
    return _reference > other._reference;
  }

  inline bool operator> (const Type *obj) const noexcept {
    return _reference > obj;
  }

  inline bool operator< (const Ptr &other) const noexcept {
    return _reference < other._reference;
  }

  inline bool operator< (const Type *obj) const noexcept {
    return _reference < obj;
  }

  inline bool operator>= (const Ptr &other) const noexcept {
    return _reference >= other._reference;
  }

  inline bool operator>= (const Type *obj) const noexcept {
    return _reference >= obj;
  }

  inline bool operator<= (const Ptr &other) const noexcept {
    return _reference < other._reference;
  }

  inline bool operator<= (const Type *obj) const noexcept {
    return _reference < obj;
  }

  inline Type& operator* () noexcept {
    return ref();
  }

  inline const Type& operator* () const noexcept {
    return ref();
  }

  inline Type* operator-> () noexcept {
    return ptr();
  }

  inline const Type* operator-> () const noexcept {
    return ptr();
  }

  template<typename... Args>
  inline static Ptr create (Args&&... args) {
    auto obj = new (std::nothrow) Type(std::forward<Args>(args)...);
    return Ptr(obj, nullptr);
  }

private:
  Type *_reference;
};


template<typename Type>
inline bool operator== (const Type *obj, const Ptr<Type> &ptr) noexcept {
  return obj == ptr->ptr();
}

template<typename Type>
inline bool operator!= (const Type* obj, const Ptr<Type>& ptr) noexcept {
  return obj != ptr->ptr();
}

template<typename Type>
inline bool operator> (const Type* obj, const Ptr<Type>& ptr) noexcept {
  return obj > ptr->ptr();
}

template<typename Type>
inline bool operator< (const Type* obj, const Ptr<Type>& ptr) noexcept {
  return obj < ptr->ptr();
}

template<typename Type>
inline bool operator>= (const Type* obj, const Ptr<Type>& ptr) noexcept {
  return obj >= ptr->ptr();
}

template<typename Type>
inline bool operator<= (const Type* obj, const Ptr<Type>& ptr) noexcept {
  return obj <= ptr->ptr();
}


CLOSE_JLIB_NS